﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MessageWorkflow.Handler
{
    public class SingleWorkflowHandler<T> : WorkflowHandler<T>
    {
        private readonly object SyncRoot = new object();
        private Dictionary<AsyncResult<T>, List<AsyncCallback>> _pendingMessages = new Dictionary<AsyncResult<T>,List<AsyncCallback>>();
        private AsyncResult<T> _currentMessage;
        private List<AsyncCallback> _currentCallbacks;

        protected override IAsyncResult BeginHandleMessage(AsyncCallback callback, T message)
        {
            lock (SyncRoot)
            {
                // Is this message currently being handeled?
                if (object.Equals(_currentMessage, message))
                {
                    if (callback != null)
                    {
                        // simply register the callback
                        _currentCallbacks.Add(callback);
                    }

                    return _currentMessage;
                }
                else
                {
                    List<AsyncCallback> messageCallbacks;
                    AsyncResult<T> messageAsyncResult;

                    var messageEntryQuery = from existingEntry in _pendingMessages
                                            where object.Equals(existingEntry.Key.State, message)
                                            select existingEntry;

                    // New message, conditionally, add it to the dictionary and register the callback
                    if (messageEntryQuery.Any())
                    {
                        var messageEntry = messageEntryQuery.Single();

                        messageAsyncResult = messageEntry.Key;
                        messageCallbacks = messageEntry.Value;
                    }
                    else
                    {
                        messageAsyncResult = new AsyncResult<T>(OnMessageCompleted, message);
                        messageCallbacks = new List<AsyncCallback>();
                        _pendingMessages.Add(messageAsyncResult, messageCallbacks);
                    }

                    messageCallbacks.Add(callback);

                    TryUpgradeFirstMessage();

                    return messageAsyncResult;
                }
            }
        }

        protected virtual void OnMessageCompleted(IAsyncResult result)
        {
            //AsyncResult<T> typedResult = (AsyncResult<T>)result;

            lock (SyncRoot)
            {
                foreach (AsyncCallback callback in _currentCallbacks)
                {
                    callback(result);
                }
            }

            TryUpgradeFirstMessage();
        }

        private void TryUpgradeFirstMessage()
        {
            lock (SyncRoot)
            {
                if (_currentMessage == null)
                {
                    if (_pendingMessages.Count > 0)
                    {
                        var nextMessage = _pendingMessages.First();

                        _pendingMessages.Remove(nextMessage.Key);

                        _currentCallbacks = nextMessage.Value;
                        _currentMessage = nextMessage.Key;

                        OnCurrentMessageChanged();
                    }
                }
            }
        }

        public AsyncResult<T> CurrentMessage
        {
            get
            {
                return _currentMessage;
            }
        }

        protected virtual void OnCurrentMessageChanged()
        {
            EventHandler currentMessageChanged = CurrentMessageChanged;
            if (currentMessageChanged != null)
                currentMessageChanged(this, EventArgs.Empty);
        }

        public event EventHandler CurrentMessageChanged;
    }
}

